<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js" type="text/javascript"></script>
<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>
<script type="text/javascript">

/* +++++++++++++++++++++++++++++++++++++++++++++++++++++

   Script-Name: Basic-SharePoint-Google-Maps-WebPart-For-SharePoint-Lists   

   Author:      Axel Schneider
   Last-Update: 11.10.2011
   Version:     1.31
   www:         http://spgooglemapswpforspl.codeplex.com
                http://axelschneider.info

   History:     11.10.2011 (v1.31)- Fixed design-bug - if a geo-marker had been selected and the sp-list-item had been selected the box around the sp-list-item was too big.
                01.08.2011 (v1.3) - Added link to information-window for marker that has the same functionality like the List-Item-Link - even with context-menu.
                31.07.2011 (v1.22)- Changed some debug-hints from German to English.
                13.07.2011 (v1.21)- Fixed bug for "hideMapUntilClick=false". 
                29.06.2011 (v1.2) - Changed pre-check; Added map-toggle; Made data available even if list-view does not show certain fields
                26.06.2011 (v1.1) - Added functionality for SharePoint 2010 and added some descriptions.
		        21.06.2011 (v1.0) - Inital version after some nights of development (only for SharePoint 2007).
   
   +++++++++++++++++++++++++++++++++++++++++++++++++++++ */

/* ***************************************************** 
   AND NOW SOME VARIABLES YOU MAY WANT TO CHANGE.
   ***************************************************** */
var mapWidth = "900px";     //the width of the map
var mapHeight = "600px";    //the height of the map

//if true the coordinates (latitude, longitude) will be used
//if false the addresses (street, zip, city, country) will be used
var useCoordinates = true;

//if true the map will not be shown by default 
//if false the map will be shown by default
var hideMapUntilClick = false;

//if 0 absolutely no warning- and error-alerts will be written to a log-div
//if 1 the warning- and error-alerts will be written to a log-div
//if 2 the warning- and error-alerts will be alerted via javascript-alert
var showWarningAndErrorAlerts = 1;

//the internal-names of list-columns (not the display-name!)
var colLinkTitleInternalName = "ows_LinkTitle";     //will be used as the title of the geo-markers
var colLongitudeInternalName = "ows_Longitude";     //useCoordinates == true
var colLatitudeInternalName = "ows_Latitude";       //useCoordinates == true
var colStreetInternalName = "ows_WorkAddress";        //useCoordinates == false
var colStreetDisplayName = "Address";             //useCoordinates == false
var colZipInternalName = "ows_WorkZip";                 //useCoordinates == false
var colCityInternalName = "ows_WorkCity";                //useCoordinates == false
var colCountryInternalName = "ows_WorkCountry";            //useCoordinates == false
var defaultCountryValue = "Germany";                //the default country which will be used if no country-name will be found in the list-column

//default position (Germany: 51.165691,10.451526) > used if no markers will be set to the map
var defaultLatitude = 51.165691;
var defaultLongitude = 10.451526;

//some language-specific messages
var resxGoogleMapsLink = "Google-Map";  //the name of new menu-point in the menu-toolbar of the sharepoint-list
var resxGoogleMapsLinkTitle = "Menu: Show or hide Google-Map";  //the title which will be visible while hovering
var resxAlertsMessageText = resxGoogleMapsLink+": There are # hints!";   //the hint which will be visible if configuration warnings or errors occured.
var resxAlertsMessageTextTitle = "Click here to show or hide the hints!";    //the title which will be visible while hovering

/* *******************************************************************
   NOW DO NOT CHANGE ANYTHING IF YOU ARE NOT FAMILIAR WITH JAVASCRIPT!
   ******************************************************************* */
var isSharePoint2010 = false;
var hasMapBeenLoadedInitially = false;

var idOfMapDiv = "divGoogleMapForSharePointList";
var idOfCustomLogDiv = "divCustomLog";
var idOfCustomLogOverview = "divCustomLogOverview";

var noOfCustomLogEntries = 0;
var noOfMaxGeocodingRequest = 10;

//the attribute-name of the column "id" > will be used for a) finding the id of a certain row and b) for building the ajax-request-url
var colID = "ID";

//now some templates for jquery-selects
var jQuerySelect_2007_GetListRowByAttributeCTXName = "table[CTXName]";
var jQuerySelect_2010_GetListRowByAttributeCTXName = "div[CTXName]";

function InitializeGoogleMapForSharePointList()
{
  BuildGoogleMapCustomLogForSharePointList();

  if(!DoPreCheckForInitializationOfGooglemapsForSharePointList())
  {
    //pre-check not successfully done > abort now!    
    customAlert("The Pre-Check has not been successfully! > Abort now.");    
    return;
  }

  DoSchemaCheckAndBuildGoogleMapIconForSharePointList();  
}

function DoSchemaCheckAndBuildGoogleMapIconForSharePointList()
{
  //get the schema-data for the list
  $.get(BuildAjaxRequestUrlForSharePointListSchemaOnly(), {}, function (xml) 
  {    
    //find all necessary internal-field-names
    var arrNeccessaryFields = new Array();
    arrNeccessaryFields.push(colLinkTitleInternalName);
    if(useCoordinates)
    {
      arrNeccessaryFields.push(colLatitudeInternalName);
      arrNeccessaryFields.push(colLongitudeInternalName);
    }
    else
    {
      arrNeccessaryFields.push(colStreetInternalName);
      arrNeccessaryFields.push(colZipInternalName);
      arrNeccessaryFields.push(colCityInternalName);
      arrNeccessaryFields.push(colCountryInternalName);
    }
  
    //check all neccessary internal-field-names
    var foundAllNeccessaryFields = true;
    for(i=0;i<arrNeccessaryFields.length;i++)
    { 
      //getting <xml><s:Schema><s:ElementType><s:AttributeType name="[internal-field-name]">
      var xmlQuery = 'xml > *:first > *:first > *[name='+arrNeccessaryFields[i]+']';
      if($(xmlQuery, xml).length<1)
      {
        foundAllNeccessaryFields = false;
        customAlert("Schema-Check failed for internal-field-name '"+arrNeccessaryFields[i]+"'. The field is not available in this list.");
      }      
    }

    //check if the neccessary fields have been found
    if(foundAllNeccessaryFields)
    {
      BuildGoogleMapIconForSharePointList();
    }    
    else
    {
      var ajaxRequestLinkTag = '<a href="'+BuildAjaxRequestUrlForSharePointListSchemaOnly()+'" target="_blank" title="Click here to call the ajax-request for this SharePoint-List-Schema manually.">ajax-request for list-schema</a>';
      customAlert("Hint: You can get the internal names of the columns by calling the "+ajaxRequestLinkTag+" for the current sharepoint-list manually.");
      customAlert("Schema-Check failed. Abort now!");
    }
  });
}

function DoPreCheckForInitializationOfGooglemapsForSharePointList()
{
  //check if this is SharePoint2010 or not 
  //if it is not 2010 it is assumed that it is 2007
  if(typeof(_fV4UI)!='undefined')
  {
    //the checked javascript-variable exists only in 2010
    isSharePoint2010 = true;    
  }
  else
  {
    isSharePoint2010 = false;    
  }

  //for the first shot: support only one table 
  //for further version we could support more tables (table[class=ms-listviewtable].length>1)
  var noOfListViews = $("table[class=ms-listviewtable]").length;
  if(noOfListViews==0)
  {    
    //no list-view exists > there is no need to show google-maps
    customAlert("There is no list-view available on this site. > No need to show google-maps. > Abort now.");
    return false;
  }
  else if(noOfListViews>1)
  {    
    //there are more than one list-view > this is not supported at the moment
    customAlert("There are more than one list-views on the site. This is not supported at the moment. > Abort now!");
    return false;
  }

  //check if multi-lookup exists
  if($("table[FieldType=LookupMulti]").length>0)
  {    
    //If there are columns in the list-view which are of type multi-lookup the ajax-call (via owssrv.dll) will return zero results. 
    var multiMsg = "Multi-lookup exists! Please remove the mulit-lookup-column or use another view (otherwise the ajax-request will receive an empty result). > Abort now!";

    multiMsg += "\n\nColumns which are of type multi-lookup are (the display-name will be shown):";

    $("table[FieldType=LookupMulti]").each(function(){
      var displayName = $(this).attr("displayName");
      multiMsg += "\n- "+displayName;
    });    

    customAlert(multiMsg);
    return false;
  }

  //check if javascript-variable exists > we need ctx to get the id of the sharepoint-list
  if(ctx==null)
  {
    //this javascript-variable is essential for getting the list-id and the list-view-id.
    customAlert("The javascript-variable 'ctx' does not exist within the html-dom. > Abort now!");
    return false;
  }

  //all checks passed - return true
  return true;
}

function BuildGoogleMapCustomLogForSharePointList()
{
  if(showWarningAndErrorAlerts!=1)
  {
    return;
  }

  var divCustomLogOverview = '<div title="'+resxAlertsMessageTextTitle+'" onclick="ToggleCustomLog();" id="'+idOfCustomLogOverview+'" style="margin: 10px; cursor: pointer; color: red; display: none;"></div>';
  $("table.ms-menutoolbar").parent().append(divCustomLogOverview);

  var divCustomLog = '<div id="'+idOfCustomLogDiv+'" style="margin: 10px; display: none;"></div>';
  $("table.ms-menutoolbar").parent().append(divCustomLog);
}

function ToggleCustomLog()
{
  //show or hide
  $("#"+idOfCustomLogDiv).toggle();
}

function ToggleGoogleMapDiv()
{
  //check if the map will be called for the first time  
  if(!hasMapBeenLoadedInitially)
  { 
    ShowGoogleMapForSharePointList();
  }

  //show or hide
  $("#"+idOfMapDiv).toggle();
}

function BuildGoogleMapIconForSharePointList()
{
  //searching for the correct position in the menu-toolbar (ms-menutoolbar)
  $("td.ms-toolbar").each(function(j){
    if($(this).attr("width")=="99%")
    {
      //insert a new menu-item before the found placeholder
      //var newMenuItem = '</internal-name-of-column><td class="ms-separator">';
      var newMenuItem = '<td class="ms-separator">';
      newMenuItem += '<img src="/_layouts/images/blank.gif" alt=""/>';
      newMenuItem += '</td>';
      newMenuItem += '<td nowrap="true" class="ms-toolbar">';
      newMenuItem += '<span title="'+resxGoogleMapsLinkTitle+'">';
      newMenuItem += '<div nowrap="nowrap" hoverinactive="ms-menubuttoninactivehover" hoveractive="ms-menubuttonactivehover" onmouseover="MMU_PopMenuIfShowing(this);MMU_EcbTableMouseOverOut(this, true)" class="ms-menubuttoninactivehover">';
      newMenuItem += '<a onclick="javascript:ToggleGoogleMapDiv();return false;" href="#" style="cursor: pointer; white-space: nowrap;">'+resxGoogleMapsLink+'</a>';
      newMenuItem += '</div>';
      newMenuItem += '</span>';
      newMenuItem += '</td>';
      $(this).before(newMenuItem);
    }
  });

  //adding map-canvas as div-tag to the dom
  var divMapCanvas = '<div id="'+idOfMapDiv+'" style="margin: 10px; display: none;"></div>';
  $("table.ms-menutoolbar").parent().append(divMapCanvas);

  //check if the map should be shown as soon as possible or if it should be hidden until the user clicked the new menu-point
  if(!hideMapUntilClick)
  {    
    ToggleGoogleMapDiv();
  }
}

//gets the complete list-schema and one row with all its values (not filtered by the current used view)
function BuildAjaxRequestUrlForSharePointListByID_Template()
{
  if(ctx!=null)
  {
    //build the url of the ajax-request
    return ctx.HttpRoot+'/_vti_bin/owssvr.dll?XMLDATA=1&List=' + ctx.listName + '&Query=*&FilterField1='+colID+'&FilterValue1=';
  }
}

//gets the list-schema and no rows
function BuildAjaxRequestUrlForSharePointListSchemaOnly()
{
  //build the url of the ajax-request
  return BuildAjaxRequestUrlForSharePointListByID_Template()+'-1';  
}

function ShowGoogleMapForSharePointList()
{ 
  //build the url of the ajax-request
  var urlTemplate = BuildAjaxRequestUrlForSharePointListByID_Template();

  //build map
  $('#'+idOfMapDiv).css({"width":mapWidth, "height":mapHeight});    
  var latlng = new google.maps.LatLng(defaultLatitude,defaultLongitude);
  var myOptions = {
      zoom: 6,
      center: latlng,
      mapTypeId: google.maps.MapTypeId.ROADMAP
  };
  var gmMap = new google.maps.Map(document.getElementById(idOfMapDiv), myOptions);
  var gmBounds = new google.maps.LatLngBounds();
  var gmGeocoder = new google.maps.Geocoder();

  var jQuerySelect_GetListRowByAttributeCTXName;
  if(isSharePoint2010)
  {
    jQuerySelect_GetListRowByAttributeCTXName = jQuerySelect_2010_GetListRowByAttributeCTXName;
  }
  else
  {
    jQuerySelect_GetListRowByAttributeCTXName = jQuerySelect_2007_GetListRowByAttributeCTXName;
  }

  //check if the number of geocodings will exceed the max-number
  if(!useCoordinates && $(jQuerySelect_GetListRowByAttributeCTXName).length > noOfMaxGeocodingRequest)
  { 
    var linkToStatusCodes = '<a title="Statuscodes of geocoding-responses from Google-Maps" target="_blank" href="http://code.google.com/intl/de-DE/apis/maps/documentation/javascript/services.html#GeocodingStatusCodes">OVER_QUERY_LIMIT-Status</a>';
    var tooManyMsg = "Hint: In the current view of the SharePoint-List there are more than "+noOfMaxGeocodingRequest+" list-entries. ";
    tooManyMsg += "This will result in an "+linkToStatusCodes+" by Google-Maps (and not all markers will be shown on the map). > You have 2 options: ";
    tooManyMsg += "a) Change your view to get no more than "+noOfMaxGeocodingRequest+" list-entries or ";
    tooManyMsg += "b) use the coordinates (longitude, latitude) of the addresses (they will be shown on the map).";
    customAlert(tooManyMsg);  
  }

  //get each row from list-view which is shown at the moment
  $(jQuerySelect_GetListRowByAttributeCTXName).each(function(j)
  {
    var lat, lng, gmLatLng, gmMarker, title, customUrl, street, city, country;
    var idOfListItem = $(this).attr(colID);
    if(isSharePoint2010)
    {
        var linkToListItem = '<table height="auto" width="calcWidthpx"><tr class="ms-alternating ms-itmhover"><td height="100%" class="ms-vb-title" onmouseover="OnChildItem(this)">';	
        linkToListItem += $(this).parent().html();
        linkToListItem += '</td></tr></table><span style="font-size:72pt;"></br></span>';
    }
    else
    {
        linkToListItem = $(this).parent().html();
        linkToListItem = linkToListItem.replace(/100%/g, "auto");   //exchange tag-attributes for width and height
    }  

    //build url for the ajax-request which reads all data for a certain row (for the current list-view)
    customUrl = urlTemplate+idOfListItem; 

    //get the data for the row
    $.get(customUrl, {}, function (xml) 
    {    
      $('xml > *:last > *', xml).each(function (i) 
      {
        //get some data from the xml-response
        title = $(this).attr(colLinkTitleInternalName);
        if(isSharePoint2010)
        { 
	    var titleLength= title.length+185;
	    linkToListItem = linkToListItem.replace('calcWidth', titleLength);
        }
        if(useCoordinates)
        {
          //getting coordinates
          lat = $(this).attr(colLatitudeInternalName);
          lng = $(this).attr(colLongitudeInternalName);

          if(typeof(lat)!='undefined' && typeof(lng)!='undefined')
          {
            gmLatLng = new google.maps.LatLng(lat, lng);
            msgForInfoWindow = linkToListItem;            //you may add more information-text here
            SetMarkerForGoogleMapForSharePointList(gmLatLng, gmMap, gmBounds, title, msgForInfoWindow);
          }        
          else
          {
            customAlert(title +" has undefined lat+lng. > Do not add marker on map.");
          }
        }
        else
        {
          //getting address
          street = $(this).attr(colStreetInternalName);
          zip = $(this).attr(colZipInternalName);
          city = $(this).attr(colCityInternalName);
          country = $(this).attr(colCountryInternalName);   
 
	      //checking received values
          if(typeof(street)=='undefined') street = "";  //optional 
          if(typeof(zip)=='undefined') zip = "";        //optional
          if(typeof(city)=='undefined') 
          {
            customAlert("The ajax-response got no city for '"+title+"'. > Do not add marker on map.");
            return;
          }
          if(typeof(country)=='undefined') country = defaultCountryValue;

          address = street+","+zip+","+city+","+country;

          //getting coordinates
          gmGeocoder.geocode( { 'address': address}, function(results, status) {            
            if (status == google.maps.GeocoderStatus.OK) 
            {
              if(results.length==0)
              {
                customAlert("Geocoding: There are no results for address '"+results[0].formatted_address+"'! Expected exactly one result. > Do not show any marker on map for this address.");
              }
              else if(results.length>1)
              {
                var msg = "Geocoding: There are too many ("+results.length+") results for given address! Expected exactly one result. > Do not show any marker on map for this address.\n\nFound addresses:\n";
                for(i=0;i<results.length;i++)
                {
                  var c = i+1;
                  msg += "\n"+c+": "+results[i].formatted_address;
                }
                customAlert(msg);
              }
              else
              {
                gmLatLng = results[0].geometry.location;
                var msgForInfoWindow = linkToListItem+"<br>";
                msgForInfoWindow += "<span style='font-size:0.8em;'>Koordinaten (Lat, Lon): "+gmLatLng+"<br>Adresse: "+results[0].formatted_address+"</span>";
                SetMarkerForGoogleMapForSharePointList(gmLatLng, gmMap, gmBounds, title, msgForInfoWindow);
              }
            }
            else
            {
              customAlert("Geocode for address '"+address+"' was not successful for the following reason: " + status);
            }
          });          
        }
                
      });     
    });    
  });

  hasMapBeenLoadedInitially = true;
}

function SetMarkerForGoogleMapForSharePointList(gmLatLng, gmMap, gmBounds, title, contentForInfoWindow)
{
  var gmMarker = new google.maps.Marker({
    position: gmLatLng,
    map: gmMap,
    title: title,
    zIndex: 0
  });
  gmBounds.extend(gmLatLng);
  gmMap.setCenter(gmBounds.getCenter());
  gmMap.fitBounds(gmBounds);

  if(contentForInfoWindow!=null && contentForInfoWindow!="")
  {
    var gmInfowindow = new google.maps.InfoWindow({
      content: contentForInfoWindow
    });
	
    google.maps.event.addListener(gmMarker, 'click', function() {
         gmInfowindow.open(gmMap,gmMarker); 
    });
  }
}

function customAlert(msg)
{
  if(msg==null || msg=="")
  {
    return;
  }
  else
  {
    var now = new Date();
    msg = now.getHours()+":"+now.getMinutes()+":"+now.getSeconds()+": "+msg;
  }

  if(showWarningAndErrorAlerts==0)
  {
    //do nothing
  }
  else if(showWarningAndErrorAlerts==1)
  {
    //do log in log-div
    msg = msg.replace(/\n/g, "<br/>");
    msg += "<br/><br/>";
    $("#"+idOfCustomLogDiv).append(msg);

    noOfCustomLogEntries++;
    $("#"+idOfCustomLogOverview).show();
    var overviewText = resxAlertsMessageText.replace(/#/g, noOfCustomLogEntries);
    $("#"+idOfCustomLogOverview).text(overviewText);    

  }
  else if(showWarningAndErrorAlerts==2)
  {
    //do alert via javascript-alert
    alert(msg);
  }
  else
  {
    //unsupported
  }
}

//call initialization after the dom has been loaded completely > so it does not matter where this piece of javascript will be inserted in the dom
$(document).ready(InitializeGoogleMapForSharePointList);

</script>